package logic.nodes;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;

import main.listeners.ListenerContainer;
import networking.messages.syncMessages.NodeSyncMessage;
import networking.messages.syncMessages.ValueType;
import fileHandling.language.properties.NodeProps;
import gameStates.absGamesStates.AbsIngameState;
import interfaces.hud.targetInfos.TargetAngles;
import logic.common.player.Player;
import logic.common.team.Team;
import logic.nodes.collision.CollidableNode;
import logic.nodes.collision.DummyType;
import logic.nodes.collision.visibility.SightType;
import logic.nodes.damagable.DamagableNode;
import logic.nodes.nodeSettings.TeamNodeSettings;
import logic.nodes.nodeSettings.upgrades.UpgradeEvent;
import logic.nodes.nodeSettings.upgrades.UpgradeListener;
import logic.ships.hunter.Hunter;
import logic.weapons.WeaponFire;

import com.jme.bounding.BoundingSphere;
import com.jme.scene.Node;

public abstract class TeamNode extends DamagableNode implements AbsTeamNode {
	
	private static final long serialVersionUID = 1L;
	
	protected Team team;
	protected TargetAngles angles;
	protected LinkedList<Player> shooters;
	protected HashSet<Player> shooterSet;
	
	protected HashMap<SightType, BoundingSphere> sightSpheres;
	
	protected ListenerContainer destructionListeners;
	
	public TeamNode(String path, String title, Node model, TeamNodeSettings settings, Team team) {
		this(path, title, model, settings, team, DummyType.None);
	}
	
	public TeamNode(String path, String title, Node model, TeamNodeSettings settings, Team team, 
			AbsIngameState ingameState) {
		this(path, title, model, settings, team, DummyType.None, ingameState);
	}
	
	public TeamNode(String path, String title, Node model, TeamNodeSettings settings, Team team, 
			DummyType dummyType) {
		this(path, title, model, settings, team, dummyType, team.getIngameState());
	}
	
	public TeamNode(String path, String title, Node model, TeamNodeSettings settings, Team team, 
			DummyType dummyType, AbsIngameState ingameState) {
		super(path, title, model, settings, dummyType, ingameState);
		
		shooters = new LinkedList<Player>();
		shooterSet = new HashSet<Player>();
		
		destructionListeners = new ListenerContainer(DestructionListener.class);
		
		sightSpheres = new HashMap<SightType, BoundingSphere>();
		
		setTeam(team);
		
		addController(new TeamNodeSphereController(this));
		
		ingameState.getHUDState().registerTeamNode(this);
	}
	
	@Override
	protected void initValueContainer() {
		super.initValueContainer();
		valueManager.changeValue(ValueType.Energy, getMaxEnergy());
	}
	
	@Override
	protected void applyDummyType() {
		super.applyDummyType();
		
		switch(dummyType) {
			case None: setStatusController(new TeamNodeController(this)); break;
			case Client: setStatusController(new TeamNodeController(this)); break;
		}
	}
	
	@Override
	protected void addUpgradeListeners() {
		super.addUpgradeListeners();
		settings.addUpgradeListener(new UpgradeListener() {
			@Override
			public void upgraded(UpgradeEvent event) {
				String key = event.getKey();
				if(key.equals(NodeProps.MaxEnergy)) changeEnergy(getMaxEnergy());
			}
		});
	}
	
	@Override
	public void increaseShield(int amount) {
		super.increaseShield(amount);
		if(getCurrentShield() == getMaxShield()) {
			shooters.clear();
			shooterSet.clear();
		}
	}
	
	public void addDestructionListener(DestructionListener l) { destructionListeners.addListener(l); }
	
	public void removeDestructionListener(DestructionListener l) { destructionListeners.removeListener(l); }
	
	public void changeEnergy(int newEnergy) {
		if(getCurrentEnergy() == newEnergy) return;
		
		valueManager.changeValue(ValueType.Energy, newEnergy);
	}
	
	@Override
	public Team getTeam() { return team; }
	
	public void setTeam(Team newTeam) {
		if(newTeam == team) return;
		team = newTeam;
		if(angles != null) angles.teamChanged();
	}
	
	@Override
	public void destroy() {
		super.destroy();
		
		detachChild(shields);
		
		Player lastShooter = shooters.pollLast();
		shooterSet.remove(lastShooter);
		if(lastShooter != null) {
			assert(!shooters.contains(lastShooter));
			assert(!shooterSet.contains(lastShooter));
		}
		
		destructionListeners.callMethod(DestructionListener.DESTROYED, new DestructionEvent(this, lastShooter));
		
		if(lastShooter == null) {
			assert(shooters.isEmpty());
			assert(shooterSet.isEmpty());
			return;
		}
		
		int destroyMoney = getDestroyMoney();
		
		if(lastShooter.getTeam() == team) {
			if(destroyMoney > lastShooter.getMoney()) destroyMoney = lastShooter.getMoney();
			lastShooter.spendMoney(destroyMoney);
		} else lastShooter.addMoney(destroyMoney);
		
		if(this instanceof Hunter) {
			Hunter hunter = (Hunter)this;
			for(Player p : shooters) {
				assert(p != lastShooter);
				p.assist(hunter.getPilot());
			}
		}
		
		shooters.clear();
		shooterSet.clear();
	}
	
	public LinkedList<Player> getShooters() { return shooters; }
	
	@Override
	public void receiveDamage(WeaponFire weaponFire) {
		receiveDamage(weaponFire, weaponFire.getDamage());
	}
	
	@Override
	public void receiveDamage(WeaponFire weaponFire, int damage) {
		assert(!isDestroyed());
		if(!ingameState.getGame().getGameCycle().hasFriendlyFire() 
		&& weaponFire.getShip().getTeam() == getTeam()) return;
		
		if(weaponFire.getShip() instanceof Hunter) {
			Player pilot = ((Hunter)weaponFire.getShip()).getPilot();
			if(!shooterSet.contains(pilot)) {
				assert(!shooters.contains(pilot));
				shooters.add(pilot);
				shooterSet.add(pilot);
			}
		} else {
			shooters.clear();
			shooterSet.clear();
		}
		
		super.receiveDamage(weaponFire, damage);
	}
	
	public abstract int getDestroyMoney();
	
	public void setAngles(TargetAngles angles) { this.angles = angles; }
	
	public TargetAngles getAngles() { return angles; }
	
	public void setMarked(boolean mark) { angles.setMarked(mark); }
	
	public int getCurrentEnergy() { return valueManager.getIntValue(ValueType.Energy); }
	
	public void decreaseEnergy(int energy) {
		int currentEnergy = getCurrentEnergy();
		if(currentEnergy - energy < 0) return;
		changeEnergy(currentEnergy - energy);
	}
	
	public void increaseEnergy(int amount) {
		int currentEnergy = getCurrentEnergy();
		int maxEnergy = getMaxEnergy();
		if(currentEnergy + amount > maxEnergy) amount = maxEnergy - currentEnergy;
		
		changeEnergy(currentEnergy + amount);
	}
	
	public int getMaxEnergy() { return getTeamNodeSettings().getMaxEnergy(); }
	
	public int getEnergyRecovery() { return getTeamNodeSettings().getEnergyRecovery(); }
	
	public TeamNodeSettings getTeamNodeSettings() { return (TeamNodeSettings)settings; }
	
	public float getViewDistance() { return 300f; }
	
	public String getFilePath() {
		String filePath = getTeamNodeSettings().getFilePath();
		return filePath.substring(0, filePath.lastIndexOf("/"));
	}
	
	public boolean isInSightSphere(CollidableNode node) {
		if(sightSpheres == null) return false;
		
		BoundingSphere sphere = sightSpheres.get(node.getSightType());
		return sphere.contains(node.getLocalTranslation());
	}
	
	public void setSightSphere(BoundingSphere sphere, SightType type) {
		assert(!sightSpheres.containsKey(type));
		sightSpheres.put(type, sphere);
	}
	
	@Override
	public boolean applySyncMessage(NodeSyncMessage shipSyncMessage) {
		if(super.applySyncMessage(shipSyncMessage)) return true;
		if(shipSyncMessage.getType() == ValueType.Energy) {
			changeEnergy(shipSyncMessage.getIntValue());
			return true;
		}
		
		return false;
	}
	
	public void activateMissionAngles(boolean enable) {
		if(angles != null) angles.activateMissionAngles(enable);
	}
	
	@Override
	public ListenerContainer getListenerContainer(Class<?> c) {
		if(c == DestructionListener.class) return destructionListeners;
		return super.getListenerContainer(c);
	}
}